using System.Collections.Generic;
using System.Linq;
using Engine;

namespace RuthlessConquest {
    public class RouteFinderModule : Module {
        public class AStar : AStar<Planet> {
            public ShipsModule ShipsModule;

            public Faction Faction;

            public Planet Destination;

            public IEnumerable<Planet> Planets;

            public override int Cost(Planet p1, Planet p2) {
                if (p2.Faction == Faction) {
                    return IntMath.Distance(p1.Position, p2.Position);
                }
                return 10 * IntMath.Distance(p1.Position, p2.Position);
            }

            public override int Heuristic(Planet p) => IntMath.ApproxDistance(p.Position, Destination.Position);

            public override void Neighbors(Planet p, DynamicArray<Planet> neighbors) {
                foreach (Planet planet in Planets) {
                    if (planet != p
                        && ShipsModule.TestReachability(p.Position, planet.Position)) {
                        neighbors.Add(planet);
                    }
                }
            }

            public override void StorageClear() {
                foreach (Planet planet in Planets) {
                    planet.AStarStorage = null;
                }
            }

            public override void StorageAdd(Planet p, object data) {
                p.AStarStorage = data;
            }

            public override object StorageGet(Planet p) => p.AStarStorage;
        }

        public AStar A = new();

        public Dictionary<(Planet, Planet), int> RouteDistances = new();

        public RouteFinderModule(Game game) : base(game) { }

        public void FindRoute(DynamicArray<Planet> result, Planet start, Planet destination, IEnumerable<Planet> planets) {
            result.Clear();
            if (start == destination) {
                return;
            }
            if (ShipsModule.TestReachability(start.Position, destination.Position)) {
                result.Add(destination);
                return;
            }
            A.ShipsModule = ShipsModule;
            A.Faction = start.Faction;
            A.Destination = destination;
            A.Planets = planets;
            if (A.FindPath(result, start)) {
                result.Reverse();
            }
            else {
                result.Clear();
            }
        }

        public DynamicArray<Planet> FindRoute(Planet start, Planet destination, IEnumerable<Planet> planets) {
            DynamicArray<Planet> result = new();
            FindRoute(result, start, destination, planets);
            return result;
        }

        public DynamicArray<Planet> FindRoute(Planet start, Planet destination, bool allowEnemyPlanets) {
            IEnumerable<Planet> planets;
            if (allowEnemyPlanets) {
                planets = PlanetsModule.Planets;
            }
            else {
                DynamicArray<Planet> dynamicArray = new(PlanetsModule.Planets.Where(p => p.Faction == start.Faction));
                if (!dynamicArray.Contains(destination)) {
                    dynamicArray.Add(destination);
                }
                planets = dynamicArray;
            }
            return FindRoute(start, destination, planets);
        }

        public int GetRouteDistance(Planet planet1, Planet planet2) {
            if (RouteDistances.Count == 0) {
                CalculateRouteDistances();
            }
            if (RouteDistances.TryGetValue((planet1, planet2), out int value)) {
                return value;
            }
            return int.MaxValue;
        }

        public void CalculateRouteDistances() {
            DynamicArray<Planet> dynamicArray = new();
            foreach (Planet planet in PlanetsModule.Planets) {
                foreach (Planet planet2 in PlanetsModule.Planets) {
                    if (planet != planet2) {
                        if (RouteDistances.TryGetValue((planet2, planet), out int value)) {
                            RouteDistances[(planet, planet2)] = value;
                            continue;
                        }
                        A.ShipsModule = ShipsModule;
                        A.Faction = Faction.None;
                        A.Destination = planet2;
                        A.Planets = PlanetsModule.Planets;
                        dynamicArray.Clear();
                        if (A.FindPath(dynamicArray, planet)) {
                            dynamicArray.Add(planet);
                            int num = 0;
                            for (int i = 1; i < dynamicArray.Count; i++) {
                                num += IntMath.Distance(dynamicArray[i - 1].Position, dynamicArray[i].Position);
                            }
                            RouteDistances[(planet, planet2)] = num;
                        }
                        else {
                            RouteDistances[(planet, planet2)] = int.MaxValue;
                        }
                    }
                    else {
                        RouteDistances[(planet, planet2)] = 0;
                    }
                }
            }
        }
    }
}